/**
 * Copyright 2007 Igor Chaves Cananéa
 *
 * This file is part of PBSFW
 *
 * PBSFW is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2 of the License.
 *
 * PBSFW is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#include <pcl.h>
#include <cstdio>
#include "trace.h"
#include "process.h"
using namespace std;

#define _CORE_SIMULATOR_

#include "taskqueue.h"
#include "simulator.h"
#include "task.h"
#include "defines.h"
#include "deleter.h"
#include "stoptask.h"
#include "scheduler.h"
#include "process.h"
#include "simulationevent.h"

namespace pbsfw {
    Simulator *Simulator::simulator = new Simulator();
    Simulator &sim = Simulator::getInstance();
    SimulatorInterface &simulator = sim;

    Simulator::Simulator()
        : sched(new TaskQueue()), stopTime(0.0), passive(*new set<Process*>),
          initProcesses(* new vector<pair<Process*, simtime_t> >),
          resetables(*new vector<Resetable*>),
          propertyHolders(*new vector<PropertyHolder*>),
          properties(*new PropertyTable), propertyNames(*new PropertyNameTable),
          started(false) {
    }

    Simulator::~Simulator() {
        delete this->sched;
        delete &this->passive;
        delete &this->initProcesses;
        delete &this->resetables;
        delete &this->propertyHolders;
        delete &this->properties;
        delete &this->propertyNames;
    }

    void Simulator::buildPropertyTable() {
        uint size = this->propertyHolders.size();
        for (uint i = 0; i < size; ++i) {
            PropertyTable t = this->propertyHolders[i]->getProperties();
            PropertyIterator itr = t.begin();
            PropertyIterator end = t.end();

            while (itr != end) {
                Property *p = *itr;
                if (this->properties.find(p) == this->properties.end()) {
                    this->properties.insert(p);
                    this->propertyNames.insert(make_pair(p->getName(), p));
                }
                ++itr;
            }
        }
    }

    PropertyTable Simulator::queryProperties(vector<string> &names) {
        PropertyTable t;
        PropertyNameIterator end;
        uint size = names.size();

        for (uint i = 0; i < size; ++i) {
            string &name = names[i];
            PropertyNameIterator itr = this->propertyNames.find(name);
            if (itr != this->propertyNames.end()) {
                end = this->propertyNames.upper_bound(name);
                while (itr != end) {
                    t.insert(itr->second);
                    ++itr;
                }
            }
        }

        return t;
    }

    simtime_t Simulator::now() const {
        return this->sched->getCurrentTime();
    }

    void Simulator::activate(Process &p, simtime_t at) {
        if (this->started) {
            if (at < this->now()) {
                at = this->now();
            }
            p.setActive(true);
            Task *t = new Task(p, at);
            this->sched->schedule(*t);
        }
        else {
            if (this->initProcesses.empty()) {
                Deleter *d = new Deleter();
                d->incRef();
                this->initProcesses.push_back(make_pair(d, 0.0));
            }
            p.incRef();
            this->initProcesses.push_back(make_pair(&p, at));
        }
    }

    void Simulator::reactivate(Process &p, simtime_t at) {
        if (!p.isActive()) {
            set<Process*>::iterator itr = this->passive.find(&p);
            if (itr != this->passive.end()) {
                this->passive.erase(itr);
            }
        }
        else {
            p.invalidateNextTask();
        }
        this->activate(p, at);
    }

    void Simulator::hold(Process &p, simtime_t howMuch) {
        simtime_t at = this->now() + howMuch;
        if (at < this->now()) {
            at = this->now();
        }

        Task *t = new Task(p, at);
        p.setNextTask(*t);
        this->sched->schedule(*t);
        co_resume();
    }

    void Simulator::passivate(Process &p) {
        p.setActive(false);
        this->passive.insert(&p);
        co_resume();
    }

    void Simulator::run(uint32_t replications, simtime_t until) {
        uint currentReplication = 0;

        this->started = true;
        this->replications = replications;
        this->buildPropertyTable();

        while (currentReplication < this->replications) {
//			fprintf(stderr, "\rCurrent replication: %d", currentReplication + 1);
            this->init(until);
            while (this->sched->hasMoreTasks()) {
                Task &t = this->sched->getNextTask();
                t.run();
                delete &t;
            }
            this->reset();
            ++currentReplication;
        }
        this->signalSimulationEnd();
        this->clear();
    }

    void Simulator::signalSimulationEnd() {
        uint size = this->initProcesses.size();

        for (uint i = 0; i < size; ++i) {
            pair<Process*, simtime_t> p = this->initProcesses[i];
            p.first->simulationEnd();
        }
    }
    void Simulator::init(simtime_t until) {
        uint size = this->initProcesses.size();

        if (until != INFINITE_TIME) {
            Task *t = new StopTask(*this->sched, until);
            this->sched->schedule(*t);
        }

        for (uint i = 0; i < size; ++i) {
            pair<Process*, simtime_t> p = this->initProcesses[i];
            activate(*p.first, p.second);
        }
    }

    void Simulator::reset() {
        uint size = this->resetables.size();
        for (uint i = 0; i < size; ++i) {
            Resetable *r = this->resetables[i];
            if (r != 0) {
                r->reset();
            }
        }

        set<Process*>::iterator itr = this->passive.begin();
        set<Process*>::iterator end = this->passive.end();

        for (; itr != end; ++itr) {
            Process *p = *itr;
            p->cleanup();
            if (p->refCount() <= 0) {
                delete p;
            }
        }
        this->passive.clear();
    }

    void Simulator::clear() {
        uint size = this->initProcesses.size();

        for (uint i = 0; i < size; ++i) {
            pair<Process*, simtime_t> p = this->initProcesses[i];
            p.first->decRef();
            if (p.first->refCount() <= 0) {
                delete p.first;
            }
        }
        this->initProcesses.clear();

        this->propertyHolders.clear();
        this->properties.clear();
        this->propertyNames.clear();

        delete this;
    }
};
